package org.smallframework.spring.path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;
import sf.tools.ArrayUtils;
import sf.tools.StringUtils;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

/**
 * spring 类扫描
 * @see org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
 */
public class SpringClassPathScanning {

    private static Logger log = LoggerFactory.getLogger(SpringClassPathScanning.class);

    protected static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    protected static ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    /**
     * metadataReaderFactory spring中用来读取resource为class的工具
     */
    protected static MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    /**
     * 根据扫描包的配置
     * 加载需要检查的方法
     */
    public static <T> Set<T> getClassResources(Function<Resource, T> func, String scanPackages) {
        String[] scanPackageArr = scanPackages.split(",");
        return getClassResources(func, scanPackageArr);
    }

    public static <T> Set<T> getClassResources(Function<Resource, T> func, String... scanPackageArr) {
        return getClassResources(func, null, scanPackageArr);
    }

    public static Set<Resource> getClassResources(URL rootClasspath, String... locationPatterns) {
        return getClassResources(r -> r, rootClasspath, locationPatterns);
    }

    /**
     * @param func
     * @param rootClasspath  class path根路径，如果不指定那么就在所有ClassPath下寻找
     * @param scanPackageArr
     * @param <T>
     * @return
     */
    public static <T> Set<T> getClassResources(Function<Resource, T> func, URL rootClasspath, String... scanPackageArr) {
        if (ArrayUtils.isNotEmpty(scanPackageArr)) {
            List<String> locationPatterns = new ArrayList<>();
            for (int i = 0; i < scanPackageArr.length; i++) {
                String basePackage = scanPackageArr[i];
                if (StringUtils.isBlank(basePackage)) {
                    continue;
                }
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + "/" + DEFAULT_RESOURCE_PATTERN;
                locationPatterns.add(packageSearchPath);
            }
            return getResources(func, rootClasspath, locationPatterns.toArray(new String[0]));
        } else {
            String packageSearchPath = "classpath:" + DEFAULT_RESOURCE_PATTERN;
            return getResources(func, rootClasspath, packageSearchPath);
        }
    }

    public static Set<Resource> getResources(URL rootClasspath, String... locationPatterns) {
        return getResources(r -> r, rootClasspath, locationPatterns);
    }

    /**
     * @param func
     * @param rootClasspath
     * @param locationPatterns
     * @param <T>
     * @return
     */
    public static <T> Set<T> getResources(Function<Resource, T> func, URL rootClasspath, String... locationPatterns) {
        ResourcePatternResolver rpr = resourcePatternResolver;
        if (rootClasspath != null) {
            rpr = new PathMatchingResourcePatternResolver(new URLClassLoader(new URL[]{rootClasspath}));
        }
        Set<T> sets = new LinkedHashSet<>();
        if (locationPatterns != null && locationPatterns.length > 0) {
            for (String locationPattern : locationPatterns) {
                if (StringUtils.isBlank(locationPattern)) {
                    continue;
                }
                try {
                    Resource[] resources = rpr.getResources(locationPattern);
                    if (ArrayUtils.isNotEmpty(resources)) {
                        for (int i = 0; i < resources.length; i++) {
                            Resource r = resources[i];
                            sets.add(func.apply(r));
                        }
                    }
                } catch (Exception e) {
                    log.error("", e);
                }
            }
        }
        return sets;
    }

    /**
     * 根据扫描包的配置
     * 加载需要检查的方法
     */
    public static <T> List<T> getClassMetadatas(Function<ClassMetadata, T> func, String scanPackages) {
        String[] scanPackageArr = scanPackages.split(",");
        return getClassMetadatas(func, scanPackageArr);
    }

    public static <T> List<T> getClassMetadatas(Function<ClassMetadata, T> func, String... scanPackageArr) {
        Set<Resource> resources = getClassResources(resource -> resource, scanPackageArr);
        List<T> list = new ArrayList<>();
        for (Resource resource : resources) {
            //检查resource，这里的resource都是class
            list.add(getClassMetadata(func, resource));
        }
        return list;
    }

    /**
     * 加载资源，判断里面的方法
     * @param resource 这里的资源就是一个Class
     * @return
     */
    public static <T> T getClassMetadata(Function<ClassMetadata, T> func, Resource resource) {
        T t = null;
        try {
            if (resource.isReadable()) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader != null) {
                    t = func.apply(metadataReader.getClassMetadata());
                }
            }
        } catch (Exception e) {
            log.error("获取类失败", e);
        }
        return t;
    }
}

